package stmlib;

/**
 * A transaction that returns a value of type T. Implement atomic() and call execute() after creating it;
 * @author Seun Osewa
 */
abstract public class TransactionT<T>{
    /** A transaction that doesn't return any value. */
    abstract public static class V extends TransactionT<Object> {
        final public Object atomic() throws Exception{ atomicV(); return null; }
        /** Like TransactionT.atomic() but doesn't require a return value */
        abstract public void atomicV() throws Exception;
    }

    /**
     * This method will be wrapped in a transaction.  It should manipulate local variables
     * and Transactional types only.  It should not manipulate global variables or do any IO.
     * It can also <b>freely</b> call other methods/functions that satisfy the above conditions.
     */
    abstract public T atomic() throws Exception;

    /**
     * Wrap atomic() in a transaction and return the value it returns.  If atomic throws an exception
     * It wraps the Exception in an instance of Exceptions.Wrap.  You have to 'unwrap' it.
     */
    final public T execute() {
        T ans = null;
        int retries = 0;
        if(Stm.data.get().parent == null) {
            while(true) {
                boolean success = false;
                try {
                    Stm.begin();
                    ans = atomic();
                    Stm.commit();
                    success = true;
                    Stm.retries.addAndGet(retries);
                    return ans;
                }
                catch (Exceptions.Retry e) { }     //execute transaction again
                catch (Exceptions.Break e) { return null; } // leave transaction immediately.
                catch (Exception e) { throw new Exceptions.Wrap(e); } // Exception ==> RuntimeException
                finally { if (!success) Stm.rollback(); }
                retries ++;
                Thread.yield(); //todo: throw new Exceptions.RetryFailure();
            }
        } else {
             boolean success = false;
             try {
                Stm.begin();
                ans = atomic();
                Stm.commit();
                success = true;
                return ans;
             }
             catch (Exceptions.Retry e) { throw e; }
             catch (Exceptions.Break e) { throw e; }
             catch (Exception e) { throw new Exceptions.Wrap(e); }
             finally { if (!success) { Stm.rollback(); } }
        }
    }
}
